package yagwl.service.util.svc;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import net.sf.jsr107cache.Cache;
import net.sf.jsr107cache.CacheException;
import net.sf.jsr107cache.CacheFactory;
import net.sf.jsr107cache.CacheManager;

public class CacheService {
  private static final Logger log = Logger.getLogger(CacheService.class
      .getName());
  private static final Map<String, CacheLoaderCallback> callbackMap = new HashMap<String, CacheLoaderCallback>();
  private static final CacheService svc = new CacheService();
  private static Cache cache;

  static {
    init();
  }

  private static void init() {
    try {
      CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
      cache = cacheFactory.createCache(Collections.emptyMap());
    } catch (CacheException e) {
      log.severe(e.toString());
    }
  }

  public void registerCacheLoaderCallback(String cacheName,
      CacheLoaderCallback callback) {
    log.config(cacheName);
    CacheLoaderCallback c = callbackMap.get(cacheName);
    if (c != null)
      return;
    callbackMap.put(cacheName, callback);
  }

  public Object get(String cacheName, Long cacheKey) {
    log.finest(cacheName + ":" + cacheKey);
    // don't check below; let it raise null pointer exception if callback
    // is not registered
    // CacheLoaderCallback callback = callbackMap.get(cacheName);
    // if (callback == null) {
    // log.severe("Cache callback not registered: " + cacheName);
    // return null;
    // }

    String combinedKey = cacheName + ":" + cacheKey;
    Object object = cache.get(combinedKey);
    if (object != null) {
      log.finest("Cache hit");
      return object;
    }
    if (object instanceof NullObject) {
      log.finest("Cache hit, null object");
      return null;
    }

    log.info("Cache miss, key: " + combinedKey);
    CacheLoaderCallback callback = callbackMap.get(cacheName);
    object = callback.load(cacheKey);

    if (object != null) {
      log.finest("Object loaded and added to cache");
      cache.put(combinedKey, object);
      return object;
    }

    log.info("Requested object does't exist");
    // insert NullObject to avoid calling callback.load() in subsequent requests
    cache.put(combinedKey, new NullObject());
    return null;
  }

  public List<?> get(String cacheName, Long[] cacheKeys) {
    return null; // TODO P4 cache array of objects
  }

  public void invalidateAll() {
    cache.clear();
  }
  
  // TODO invalidate cache by cache name

  public void invalidate(String cacheName, Long cacheKey) {
    log.finest(cacheName);
    cache.remove(cacheName + ":" + cacheKey);
  }

  public interface CacheLoaderCallback {
    public Object load(Long objectId);

    public List<?> loadList(List<Long> objectIds);
  }

  private class NullObject {
  }

  public static CacheService getInstance() {
    return svc;
  }

}
